#include "IOchannelProxy.h"

#include "Eventloop.h"
#include "EventloopProxy.h"
#include "IOchannel.h"
#include "log/Logger.h"
#include "utility/assertion.h"
#include "wrap/poll.h"

namespace _ {

static constexpr int FLAG_IDLE = 0;

static int FLAG_READ() {
    static const int READ = Wrap::poll_in() | Wrap::poll_pri();
    return READ;
}

static int FLAG_WRITE() {
    static const int WRTIE = Wrap::poll_out();
    return WRTIE;
}

static std::string events_string(const std::string &name, int fd, int events) {
    auto result = name + '#' + std::to_string(fd) + ": ";
    if (static_cast<bool>(events & Wrap::poll_in())) {
        result += "IN,";
    }
    if (static_cast<bool>(events & Wrap::poll_pri())) {
        result += "PRI,";
    }
    if (static_cast<bool>(events & Wrap::poll_out())) {
        result += "OUT,";
    }
    if (static_cast<bool>(events & Wrap::poll_err())) {
        result += "ERR,";
    }
    if (static_cast<bool>(events & Wrap::poll_hup())) {
        result += "HUP,";
    }
    if (static_cast<bool>(events & Wrap::poll_nval())) {
        result += "NVAL,";
    }
    if (static_cast<bool>(events & Wrap::poll_remove())) {
        result += "REMOVE,";
    }
    if (static_cast<bool>(events & Wrap::poll_rdhup())) {
        result += "RDHUP,";
    }
    return result;
}

}

IOchannelProxy::~IOchannelProxy() {
    assert(!_handlingEvent);
    if (_eventloopProxy.eventloop().is_in_this_thread()) {
        assert(!_eventloopProxy.has(*this));
    }
}

int IOchannelProxy::fd() const {
    return _channel.fd();
}

std::string IOchannelProxy::listen_events_string() const {
    return _::events_string(name(), fd(), _listenEvents);
}

const std::string &IOchannelProxy::name() const {
    return _channel.name();
}

std::string IOchannelProxy::return_events_string() const {
    return _::events_string(name(), fd(), _returnEvents);
}

void IOchannelProxy::disable() {
    _listenEvents = _::FLAG_IDLE;
}

void IOchannelProxy::disable_reading() {
    _listenEvents &= ~_::FLAG_READ();
}

void IOchannelProxy::disable_writing() {
    _listenEvents &= ~_::FLAG_WRITE();
}

void IOchannelProxy::enable_reading() {
    _listenEvents |= _::FLAG_READ();
}

void IOchannelProxy::enable_writing() {
    _listenEvents |= _::FLAG_WRITE();
}

bool IOchannelProxy::is_idling() const {
    return _listenEvents == _::FLAG_IDLE;
}

bool IOchannelProxy::is_reading() const {
    return static_cast<bool>(_listenEvents & _::FLAG_READ());
}

bool IOchannelProxy::is_writing() const {
    return static_cast<bool>(_listenEvents & _::FLAG_WRITE());
}

void IOchannelProxy::handle_event(Utility::timepoint t) {
    if (!_tie.has_value()) {
        _event_handler(t);
        return;
    }
    if (_tie.value().expired()) {
        return;
    }
    auto guard = _tie.value().lock();
    _event_handler(t);
}

void IOchannelProxy::_close_event_handler() const {
    static const auto CLOSE = Wrap::poll_hup();
    if (static_cast<bool>(_returnEvents & CLOSE) &&
        !static_cast<bool>(_returnEvents & Wrap::poll_in())) {
        LOG_WARN << _channel.name() << " channel fd= " << _channel.fd()
                 << " POLLHUP";
        _closeCallback();
    }
}

void IOchannelProxy::_error_event_handler() const {
    static const auto ERROR = Wrap::poll_err() | Wrap::poll_nval();
    if (static_cast<bool> (_returnEvents & ERROR)) {
        _errorCallback();
    }
}

void IOchannelProxy::_event_handler(Utility::timepoint t) {
    _handlingEvent = true;
    _close_event_handler();
    _error_event_handler();
    _read_event_handler(t);
    _write_event_handler();
    static const auto INVALID = Wrap::poll_nval();
    if (static_cast<bool>(_returnEvents & INVALID)) {
        LOG_WARN << "channel fd=" << _channel.fd() << " POLLNVAL";
    }
    _handlingEvent = false;
}

void IOchannelProxy::_read_event_handler(Utility::timepoint t) const {
    static const auto IN = Wrap::poll_in() | Wrap::poll_pri() | Wrap::poll_rdhup();
    if (static_cast<bool>(_returnEvents & IN)) {
        _readCallback(t);
    }
}

void IOchannelProxy::_write_event_handler() const {
    static const auto OUT = Wrap::poll_out();
    if (static_cast<bool>(_returnEvents & OUT)) {
        _writeCallback();
    }
}
